#include "modules/WebRadio.h"
#include <cstring>
#include "util.h"
#include <sstream>

#include "hw/Samsung_20T202DA2JA.h"

#define MSG_CMD_NEXT 0
#define MSG_CMD_PREVIOUS 1

#define DELAY_START_STREAMING 250//250 seams good 500

void WebRadio::process_one_tag (const GstTagList * list, const gchar * tag, gpointer user_data)
{
    int i, num;
    WebRadio* web_radio = (WebRadio*) user_data;

    num = gst_tag_list_get_tag_size (list, tag);
    for (i = 0; i < num; ++i) {
        const GValue *val;

        /* Note: when looking for specific tags, use the gst_tag_list_get_xyz() API,
        * we only use the GValue approach here because it is more generic */
        val = gst_tag_list_get_value_index (list, tag, i);
        if (G_VALUE_HOLDS_STRING (val)) {
            //g_print ("\t%20s : %s\n", tag, g_value_get_string (val));
            if (strcmp(tag, "title") == 0){
                web_radio->m_screen->scroll_on_2nd_line(std::string(g_value_get_string(val)));
            }
        } else if (G_VALUE_HOLDS_UINT (val)) {
            //g_print ("\t%20s : %u\n", tag, g_value_get_uint (val));
        } else if (G_VALUE_HOLDS_DOUBLE (val)) {
            //g_print ("\t%20s : %g\n", tag, g_value_get_double (val));
        } else if (G_VALUE_HOLDS_BOOLEAN (val)) {
            //g_print ("\t%20s : %s\n", tag, (g_value_get_boolean (val)) ? "true" : "false");
        } else if (GST_VALUE_HOLDS_BUFFER (val)) {
            GstBuffer *buf = gst_value_get_buffer (val);
            guint buffer_size = gst_buffer_get_size (buf);

            g_print ("\t%20s : buffer of size %u\n", tag, buffer_size);
        } else if (GST_VALUE_HOLDS_DATE_TIME (val)) {
            GstDateTime *dt = (GstDateTime*)g_value_get_boxed (val);
            gchar *dt_str = gst_date_time_to_iso8601_string (dt);

            //g_print ("\t%20s : %s\n", tag, dt_str);
            g_free (dt_str);
        } else {
            //g_print ("\t%20s : tag of type '%s'\n", tag, G_VALUE_TYPE_NAME (val));
        }
    }
}

gboolean WebRadio::bus_callback_s (GstBus* bus, GstMessage* msg, gpointer data){
    WebRadio* web_radio = (WebRadio*) data;
    GMainLoop* loop = web_radio->m_loop;

    switch (GST_MESSAGE_TYPE (msg)) {
    case GST_MESSAGE_EOS:{
        g_print ("End of stream\n");
        LOG << "GST_MESSAGE_EOS m_async_received:" << web_radio->m_async_received << " m_stream_received_data:" << web_radio->m_stream_received_data << std::endl;
        web_radio->m_stop_received = true;
        //if (!(web_radio->m_test_async_start && !web_radio->m_async_received)){
        //if (web_radio->m_async_received){
        if (!(web_radio->m_stream_received_data && !web_radio->m_async_received)){
            g_main_loop_quit (loop);
        }
        break;
    }
    case GST_MESSAGE_ERROR:{
        gchar  *debug;
        GError *error;

        gst_message_parse_error (msg, &error, &debug);
        g_free (debug);
        g_printerr ("Error: %s\n", error->message);
        g_error_free (error);

        ////
        LOG << "GST_MESSAGE_ERROR m_async_received:" << web_radio->m_async_received << " m_stream_received_data:" << web_radio->m_stream_received_data << std::endl;
        
        gst_bus_post(web_radio->m_bus, gst_message_new_eos(NULL));
        break;
    }
    case GST_MESSAGE_TAG:{
        GstTagList* tags;// = NULL;
        gst_message_parse_tag(msg, &tags);
        
        //g_print ("Got tags from element %s:\n", GST_OBJECT_NAME (msg->src));
        gst_tag_list_foreach (tags, process_one_tag, data);//NULL);
        //g_print ("\n");
        gst_tag_list_unref (tags);
        break;
    }
    case GST_MESSAGE_BUFFERING:{
        ////TODO post last buff % received to detect network problems
        //// is this fixed now 2014/11/13
        gint percent = 0;
        gst_message_parse_buffering (msg, &percent);
        g_print ("Buffering %u %\n", percent);
        
        //data has been received, default alsa sink is created within gstreamer
        web_radio->m_stream_received_data = true;
        
        break;
    }
    case GST_MESSAGE_ASYNC_START:{
        ////test only
        web_radio->m_test_async_start = true;
        LOG << "\n\n++++++++++++ GST_MESSAGE_ASYNC_START\n\n" << std::endl;
        break;
    }
    case GST_MESSAGE_ASYNC_DONE:{
        ////TODO post last buff % received to detect network problems
        LOG << "++++++++++++ GST_MESSAGE_ASYNC_DONE" << std::endl;
        //NOTE this never happens if main_loop is not started
        //... it means that shoudl not stopp main loop if waiting for ASYNC_DONE
        //... and it should remove the issue of sync resource busy
        web_radio->m_async_received = true;
        if (web_radio->m_stop_received){
            LOG << "\n\n QUIT WAITED FOR ASYNC \n\n" << std::endl;
            //gst_bus_post(web_radio->m_bus, gst_message_new_eos(NULL));
            g_main_loop_quit (loop);
        }
        break;
    }
    case GST_MESSAGE_STATE_CHANGED:{
        ////TODO post last buff % received to detect network problems
        //LOG << "++++++++++++ GST_MESSAGE_STATE_CHANGED" << std::endl;
        break;
    }
    default:
        //g_print("gst bus received msg of non handled type DOES THIS HANDLE NO INTERNET ? \n");
        break   ;
    }
    return TRUE;
}

//#include <unistd.h>
void WebRadio::init(){
    WebStationDataStore ds("../data/web_station_list.xml");
    ds.load();
    
    while (! ds.m_station_list.empty()){
        //LOG << ds.m_station_list.front()->name << std::endl;
        m_station_list.push_back(ds.m_station_list.front());
        ds.m_station_list.pop_front();
    }

    m_current_station = m_station_list.begin();
}

int WebRadio::threadHandler(){
    LOG << "starting web_radio thread handler" << std::endl;
    m_screen->write((*m_current_station)->name, true);
    start_streaming((*m_current_station)->address.c_str());
    m_change_station_flag = false;
    
    std::shared_ptr<Message> m = 0;
    while (m_shouldRun){
        //check if any message wiating in queue
        m = m_q.deque();
        while (0 != m && m_shouldRun){
            if (MSG_CMD_NEXT == m->cmd){
                m_current_station ++;
                if (m_current_station == m_station_list.end())
                    m_current_station = m_station_list.begin();
            }else if (MSG_CMD_PREVIOUS == m->cmd){
                if (m_current_station == m_station_list.begin())
                    m_current_station = m_station_list.end();
                m_current_station --;
            }
            //stop previous stream if any, do no wait for thread to join
            //stop_streaming(true);
            stop_streaming(false);
            //write station name
            m_screen->write((*m_current_station)->name , true);
            m_screen->scroll_on_2nd_line(std::string(""));
            //radio station will actually streams after some delay only (avoids troubles when switching station very fast)
            m_change_station_flag = true;
            m_change_station_tp = std::chrono::high_resolution_clock::now();
            //process until queue is empty
            m = m_q.deque();
        }
        
        if (m_change_station_flag){
            //check if delay occured and should really start stream
            std::chrono::high_resolution_clock::time_point now = std::chrono::high_resolution_clock::now();
            std::chrono::milliseconds d = std::chrono::duration_cast<std::chrono::milliseconds>(now - m_change_station_tp);
            if (d.count() > DELAY_START_STREAMING)
            {
                start_streaming((*m_current_station)->address.c_str());
                m_change_station_flag = false;
            }
        }
        
        std::this_thread::sleep_for(std::chrono::milliseconds(10));
    }
    stop_streaming();
    m_screen->clear();
    m_screen->scroll_on_2nd_line(std::string(""));
    LOG << "Quitting WebRadio::threadHandler... 2" << std::endl;
    return 0;
}

int WebRadio::next(){
    enqueMessage(std::shared_ptr<Message> (new Message(MSG_CMD_NEXT)));
    return 0;
}

int WebRadio::previous(){
    enqueMessage(std::shared_ptr<Message> (new Message(MSG_CMD_PREVIOUS)));
    return 0;
}

int WebRadio::start_streaming(const char* addr){
    LOG << "start_streaming ----------- " << std::endl;
    m_gst_loop_thread = std::thread(&WebRadio::gst_loop_threadHandler, this, addr);

    return 0;
}


int WebRadio::stop_streaming(bool noWait){
    LOG << "stop_streaming xxxxxxxxxxxx " << std::endl;
    if (m_loop != 0){
        /*m_stop_received = true;
        if (!(m_stream_received_data && ! m_async_received)){
            gst_bus_post(m_bus, gst_message_new_eos(NULL));
        }*/
        gst_bus_post(m_bus, gst_message_new_eos(NULL));
    }
    if (!noWait && m_gst_loop_thread.joinable()){
        //LOG << "stream thread join" << std::endl;
        m_gst_loop_thread.join();
    }
    return 0;
}

void WebRadio::reload_current_stream(){
    //TODO BUG this block coordinator if previous not streaming ???
    stop_streaming();
    start_streaming((*m_current_station)->address.c_str());
}

int WebRadio::gst_loop_threadHandler(const char* addr){
    LOG << "receive_stream thread started : " << addr << std::endl;
    
    m_stream_received_data = false;
    m_async_received = false;
    m_stop_received = false;
    
    m_test_async_start = false;
    
    gst_init(NULL, NULL);
    GstElement *play;
    m_loop = g_main_loop_new (NULL, FALSE);
    play = gst_element_factory_make ("playbin" , "play");
    g_object_set(G_OBJECT(play), "uri", addr, NULL);
    
    //set volume to 50% because amp is powerfull enough, and makes volume control hard at low volume
    gdouble vol = 1.0;
    g_object_set(G_OBJECT(play), "volume", (gdouble)0.3/*0.5*/, NULL);
    
    m_bus = gst_pipeline_get_bus (GST_PIPELINE (play));
    guint bus_watch_id = gst_bus_add_watch (m_bus, WebRadio::bus_callback_s, this);
    ///this is from example in gstreamer.freedesktop.org
    ///I guess it's because when adding bus, it increments ref
    gst_object_unref(m_bus);

    /* Set the pipeline to "playing" state*/
    g_print ("Now playing: %s\n", addr);
    gst_element_set_state (play, GST_STATE_PLAYING);

    /* Iterate */
    g_main_loop_run (m_loop);
    
    g_print ("Deleting pipeline\n");
    GstStateChangeReturn r = gst_element_set_state (play, GST_STATE_NULL);
    
    if (GST_STATE_CHANGE_FAILURE == r){
        LOG << "!!!!!!!  FAILURE when set state to NULL !!!!!!" << std::endl;
    }
    
    ///this is from example in gstreamer.freedesktop.org  <---- meanin proper way os unref
    gst_object_unref(GST_OBJECT(play));
    g_source_remove (bus_watch_id);
    g_main_loop_unref (m_loop);
    ////// end freedesktop example
    
    //try to get the auto audio sink to see if can detetc error before it happens
    //GstElement sink = g_object_set_get(G_OBJECT(play), "audio_sink"
    
    ////keep this to show what NOT to do !!
    //this would lead to crash if trying to send EOS to unrefed bus
    //g_source_remove (bus_watch_id);
    //gst_object_unref(GST_OBJECT (m_bus));
    //un-necessary plus triggers errors related to state change which leads to alsa sink resource busy error
    //gst_object_unref (GST_OBJECT (play));
    //g_main_loop_unref (m_loop);
    
    m_loop = 0;
    
    //this screwss future streaming DO NOT DO !!
    //gst_deinit();
    
    g_print ("Deleting pipeline finished\n");
    
    
    ///// This is fixed now, but this comment remains in case it shows up again !!
    //maybe a solution here
    //http://gstreamer.freedesktop.org/data/doc/gstreamer/head/gstreamer/html/GstElement.html#gst-element-get-state
    
    //TODO problem with alsa sink resource busy when after this happens :
    /* (wifisoundsystem:2300): GStreamer-CRITICAL **: 
Trying to dispose element alsasink30, but it is in READY instead of the NULL state.
You need to explicitly set elements to the NULL state before
dropping the final reference, to allow them to clean up.
This problem may also be caused by a refcounting bug in the
application or some element.

AL lib: alsa_open_playback: Could not open playback device 'default': Device or resource busy
AL lib: alsa_open_playback: Could not open playback device 'default': Device or resource busy
Error: The autoaudiosink element is not working. */

    return 0;
}


